{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from matplotlib import pyplot\n",
    "from numpy import unique\n",
    "from numpy import argmax\n",
    "from tensorflow.keras.datasets.mnist import load_data\n",
    "from tensorflow.keras import Sequential\n",
    "from tensorflow.keras.layers import Dense\n",
    "from tensorflow.keras.layers import Conv2D\n",
    "from tensorflow.keras.layers import MaxPool2D\n",
    "from tensorflow.keras.layers import Flatten\n",
    "from tensorflow.keras.layers import Dropout\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "train = pd.read_csv(\"../Data/mnist_train.csv\", header=None)\n",
    "test = pd.read_csv(\"../Data/mnist_test.csv\", header=None)\n",
    "\n",
    "trainX = train[train.columns[1:]].to_numpy().reshape((-1, 28, 28))\n",
    "trainy = train[train.columns[0]].to_numpy()\n",
    "testX = test[test.columns[1:]].to_numpy().reshape((-1, 28, 28))\n",
    "testy = test[test.columns[0]].to_numpy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train: X=(60000, 28, 28), y=(60000,)\n",
      "Test: X=(10000, 28, 28), y=(10000,)\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWAAAAD8CAYAAABJsn7AAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJztnXd4VEX3xz9DCT2h/jACJtIF1IiIUXkFFFCxgSIaKRJRitJUFOG1gIioFBFQERRsvBRBioWigIIgCChSA9K7hB4gEJI9vz82e82yCdl+7+7O53nmIXvr2S/nzs6cO3NGiQgajUajCT4FzDZAo9FoIhVdAWs0Go1J6ApYo9FoTEJXwBqNRmMSugLWaDQak9AVsEaj0ZiETxWwUupupdRWpdR2pdTL/jIq1NG6uKI1cUVr4kqkaaK8HQeslCoIbAOaA/uB1UCSiGz2n3mhh9bFFa2JK1oTVyJRE19awA2B7SKyU0QygKnAg/4xK6TRuriiNXFFa+JKxGlSyIdzKwH7cnzeD9x8uROUUpacdiciyo+X80gXq2oCHBWRCn66ltbEFf38uBI2muCmr/hSAbuFUqoL0CXQ9wklQkSTPcG8mdYkd0JEl6ASIpq45Su+VMAHgCo5PlfO3uaEiIwHxoOlf638Sb66aE20JujnJzciThNfYsCrgRpKqauVUlHAY8Bc/5gV0mhdXNGauKI1cSXiNPG6BSwimUqpHsACoCAwUUQ2+c2yEEXr4orWxBWtiSuRqInXw9C8uplFuwt+fgnnEf7W5MYbbwSgR48edOzYkS+++AKAMWPG8Mcff3hyqbUi0sCftrmLVf0EEzUB6+oSTs+PH3HLV/RMOI1GozGJkGoBFyxYEICYmBin7T169KB48eLUqlULgGeffZbhw4eTlJQEwPnz53n77bcBGDRokMt1w+UXPCEhgcWLFwMQHR3ttO/UqVOUK1fOk8tFRAv4zjvvZPLkyQA0btyYrVu3Xu7wsG4Bv/LKK4D9GSlQoABNmjQB4JdffrnseeHy/PgZt3wl4MPQvOWqq64iKioKgFtvvZVGjRpRunRpAB5++OFcz9m/fz8Ao0ePpnXr1qSlpQHw119/5etEoU7Dhg2ZOXOm8eMkIqSlpZGRkQFAuXLlSExMNMIQju2hwu233w7Yv8esWbP8dt2bbrqJ1atX++16oUqnTp3o168fADabDbD7kCawWK4CTkhIAGDx4sUuLd3LYbPZjF/wM2fOMHnyZA4dOgTAiRMn8mvZhCTFixenfv36AHz11VfExsY67f/777959913AZg6dSrLly83NBo6dGhwjfURR2usRo0afquACxQowNVXX01cXBwASpnWkDOduLg4ihYtarYZQefmm+3zPNq3b0/jxo2pW7eusa9v374cPHgQgEaNGvHVV1+xatUqv95fx4A1Go3GJCzXAt67dy8Ax44du2wLeNWqVZw8eRKApk2bkpGRwZdffhkUG63Cxx9/bMS5c6N+/fqULFkSsMfxmjRpwnXXXRcs8/xKx44dAfjtt9/8ds3Y2FiefvppvvrqKwBSUlL8du1QolmzZvTs2dP4nJKSwn333cc///xjolWB59FHH+X9998HoHz58iil+PnnnwGoUKECw4YNM45VSlGhQgUee+wxv9pguQr4+PHjALz44ovcd999APz555+MHj3aOGbdunU0b96cs2fPAlC3bl169+4dfGNN5MYbb+Tee+916jb/8ssvfPvttwAMHz6cgwcP8ueffwL2MMwdd9wRst3sAgX831n75JNPAHuoJhJp1KgRAJMmTXJq7AwbNow9e4I+6zooFCpkr/IaNGjAhAkTKF68OABLly5l8ODB/PrrrwAUKVKE6dOn06JFC+PcNWvW+N0eHYLQaDQasxCRoBVAPCnR0dESHR0tSikZP368ZGVlSVZWliQlJXl0nfxKMDXwVZOEhARJSEiQ48ePS2ZmplG+/fZbKVmypNx7771y7733Sv/+/aVChQpO52ZlZUlaWpqkpaVJ/fr187vXGqtoct1118nZs2fl7Nmz8uWXX/rt/33FihVis9kkMTFREhMT3TnHNE288ZX8yoQJE2TChAnGc7Vo0SJZtGhRWD8/nTp1kk6dOhnPzbx582TevHkSHR3tdFz79u2dnq89e/a4PE/+8BVLi5WzDBs2zHCUxYsXS4ECBfzmiKHiQDVr1pTJkyfL5MmTJSsrS/755x9Zt26drFu3Ttq0aZPv+VlZWYZDTZ482bKVzaW2vPzyy2Kz2cRms/mlAq5YsaJUrFhRDh06JDabTapUqSJVqlTx20NlBV/Jr5QvX954ni5evCipqanStGlTadq0adg+P4MHDza+c2ZmpowePdpo5F167JYtW5wq4Acf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\n",
      "text/plain": [
       "<Figure size 432x288 with 25 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "print('Train: X=%s, y=%s' % (trainX.shape, trainy.shape))\n",
    "print('Test: X=%s, y=%s' % (testX.shape, testy.shape))\n",
    "\n",
    "for i in range(25):\n",
    "    pyplot.subplot(5, 5, i+1)\n",
    "    pyplot.imshow(trainX[i], cmap=pyplot.get_cmap('gray'))\n",
    "\n",
    "pyplot.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "trainX = trainX.reshape((trainX.shape[0], trainX.shape[1], trainX.shape[2], 1))\n",
    "testX = testX.reshape((testX.shape[0], testX.shape[1], testX.shape[2], 1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(28, 28, 1)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "in_shape = trainX.shape[1:]\n",
    "in_shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n_classes = len(unique(trainy))\n",
    "n_classes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Normalize\n",
    "trainX = trainX.astype('float32') / 255.0\n",
    "testX = testX.astype('float32') / 255.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create CNN Model\n",
    "model = Sequential()\n",
    "model.add(Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_uniform', input_shape=in_shape))\n",
    "model.add(MaxPool2D((2, 2), strides=(2,2)))\n",
    "model.add(Conv2D(32, (2, 2), activation='relu', kernel_initializer='he_uniform', input_shape=in_shape))\n",
    "model.add(MaxPool2D((2, 2), strides=(2,2)))\n",
    "model.add(Flatten())\n",
    "model.add(Dense(500, activation='relu', kernel_initializer='he_uniform'))\n",
    "# model.add(Dropout(0.5))\n",
    "model.add(Dense(n_classes, activation='softmax'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "conv2d (Conv2D)              (None, 26, 26, 32)        320       \n",
      "_________________________________________________________________\n",
      "max_pooling2d (MaxPooling2D) (None, 13, 13, 32)        0         \n",
      "_________________________________________________________________\n",
      "conv2d_1 (Conv2D)            (None, 12, 12, 32)        4128      \n",
      "_________________________________________________________________\n",
      "max_pooling2d_1 (MaxPooling2 (None, 6, 6, 32)          0         \n",
      "_________________________________________________________________\n",
      "flatten (Flatten)            (None, 1152)              0         \n",
      "_________________________________________________________________\n",
      "dense (Dense)                (None, 500)               576500    \n",
      "_________________________________________________________________\n",
      "dense_1 (Dense)              (None, 10)                5010      \n",
      "=================================================================\n",
      "Total params: 585,958\n",
      "Trainable params: 585,958\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 60000 samples\n",
      "Epoch 1/10\n",
      "60000/60000 [==============================] - 16s 266us/sample - loss: 0.1441 - accuracy: 0.9563\n",
      "Epoch 2/10\n",
      "60000/60000 [==============================] - 15s 258us/sample - loss: 0.0442 - accuracy: 0.9862\n",
      "Epoch 3/10\n",
      "60000/60000 [==============================] - 16s 264us/sample - loss: 0.0281 - accuracy: 0.9913\n",
      "Epoch 4/10\n",
      "60000/60000 [==============================] - 16s 263us/sample - loss: 0.0203 - accuracy: 0.9934\n",
      "Epoch 5/10\n",
      "60000/60000 [==============================] - 16s 274us/sample - loss: 0.0137 - accuracy: 0.9957\n",
      "Epoch 6/10\n",
      "60000/60000 [==============================] - 16s 268us/sample - loss: 0.0102 - accuracy: 0.9970\n",
      "Epoch 7/10\n",
      "60000/60000 [==============================] - 16s 260us/sample - loss: 0.0095 - accuracy: 0.9969\n",
      "Epoch 8/10\n",
      "60000/60000 [==============================] - 15s 256us/sample - loss: 0.0078 - accuracy: 0.9974- loss: 0.0077 - accuracy: \n",
      "Epoch 9/10\n",
      "60000/60000 [==============================] - 15s 257us/sample - loss: 0.0057 - accuracy: 0.9981\n",
      "Epoch 10/10\n",
      "60000/60000 [==============================] - 15s 257us/sample - loss: 0.0043 - accuracy: 0.9987\n",
      "Time: 157.58382892608643\n",
      "CPU times: user 10min 3s, sys: 9min 8s, total: 19min 12s\n",
      "Wall time: 2min 37s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "# Train\n",
    "start_time = time.time()\n",
    "model.fit(trainX, trainy, epochs=10, batch_size=128, verbose=1)\n",
    "elapsed_time = time.time() - start_time\n",
    "print(f\"Time: {elapsed_time}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy: 0.991\n"
     ]
    }
   ],
   "source": [
    "# Evaluate\n",
    "loss, acc = model.evaluate(testX, testy, verbose=0)\n",
    "print('Accuracy: %.3f' % acc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.7.5 64-bit ('tf2': conda)",
   "language": "python",
   "name": "python37564bittf2condaf9656480b0924f55a4cf395f6bffedd2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
